using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using OpenWaves.Collections;

namespace OpenWaves
{
    public partial class RelativeUrl : Url
    {
        public static RelativeUrl FromPath(UrlPath path)
        {
            Contract.Requires<ArgumentNullException>(path != null);

            return new RelativeUrl(new Uri(path.ToString(), UriKind.Relative));
        }

        public static RelativeUrl Create(UrlPath path, string fragment, IEnumerable<KeyValuePair<string, object>> query)
        {
            Contract.Requires<ArgumentNullException>(path != null);
            Contract.Requires<ArgumentNullException>(query != null);

            return new RelativeUrl(new Uri(GetUrlString(path, fragment, UrlQueryCollection.FromNameValueDictionary(query)), UriKind.Relative));
        }

#if !SILVERLIGHT
        [NonSerialized]
#endif
        private readonly UrlPath path;
#if !SILVERLIGHT
        [NonSerialized]
#endif
        private readonly UrlQueryCollection query;
#if !SILVERLIGHT
        [NonSerialized]
#endif
        private readonly string fragment;

        public override UrlPath Path
        {
            get { return this.path; }
        }

        public override UrlQueryCollection Query
        {
            get { return this.query; }
        }

        public override string Fragment
        {
            get { return this.fragment; }
        }


        public RelativeUrl(Uri uri)
            : base(uri)
        {
            Contract.Requires<ArgumentNullException>(uri != null);
            Contract.Requires<ArgumentException>(uri.IsAbsoluteUri == false);

            ParseUri(uri, out this.path, out this.fragment, out this.query);
        }

        private static void ParseUri(Uri uri, out UrlPath parsedPath, out string parsedFragment, out UrlQueryCollection parsedQuery)
        {
            String pathAndQueryString;
            var fragmentSeparatorIndex = uri.OriginalString.IndexOf('#');
            if (fragmentSeparatorIndex < 0)
            {
                pathAndQueryString = uri.OriginalString;
                parsedFragment = string.Empty;
            }
            else
            {
                pathAndQueryString = uri.OriginalString.Substring(0, fragmentSeparatorIndex);
                parsedFragment = uri.OriginalString.Substring(fragmentSeparatorIndex + 1);
            }

            var querySaperatorIndex = pathAndQueryString.IndexOf('?');
            if (querySaperatorIndex < 0)
            {
                parsedPath = UrlPath.Parse(pathAndQueryString);
                parsedQuery = UrlQueryCollection.Empty;
            }
            else
            {
                parsedPath = UrlPath.Parse(pathAndQueryString.Substring(0, querySaperatorIndex));
                parsedQuery = UrlQueryCollection.Parse(pathAndQueryString.Substring(querySaperatorIndex));           
            }
        }

        public override AbsoluteUrl ResolveAgainst(AbsoluteUrl baseUrl)
        {
            return new AbsoluteUrl(new Uri(baseUrl, this));
        }

        protected override string ToUrlString()
        {
            return GetUrlString(this.Path, this.Fragment, this.Query);
        }

        private static string GetUrlString(UrlPath path, string fragment, IEnumerable<KeyValuePair<string, string>> query)
        {
            var b = new StringBuilder(path.ToString());			
            AppendFragmentAndQuery(b, fragment, query);
            return b.ToString();
        }

        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
        public new RelativeUrlBuilder GetBuilder()
        {
            return RelativeUrlBuilder.From(this);
        }

        protected override UrlBuilder CreateBuilder()
        {
            return this.GetBuilder();
        }

        public override string ToUnescapedString()
        {
            var b = new StringBuilder(path.ToUnescapedString());
            AppendFragmentAndQuery(b, this.Fragment, this.Query);

            return b.ToString();            
        }

        private static void AppendFragmentAndQuery(StringBuilder b, string fragment, IEnumerable<KeyValuePair<string, string>> query)
        {            
            if (query.Any())
                b.Append("?").Append(query.ToString());
			if (String.IsNullOrEmpty(fragment) == false)
                b.Append("#").Append(EncodeFragment(fragment));
        }

        private static readonly string[] InvalidFragmentChars=new[]{"<",">","\"","#"};

        private static string EncodeFragment(string s)
        {
            var sb = new StringBuilder(s);
            foreach (string invalidFragmentChar in InvalidFragmentChars)
            {
                sb.Replace(invalidFragmentChar, Encode(invalidFragmentChar));
            }

            return sb.ToString();
        }
    }
}
